using System;
using System.Collections.Generic;
using System.IO;
using System.Threading;

using BlackHen.Threading;

namespace TaskLib
{
    /// <summary>
    /// Loads the plugins from the specified directory, schedules and executes them.
    /// </summary>
    public class TaskPoller
    {

        #region Private Constants

        private static readonly log4net.ILog _log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);

        private const int WAIT_TIME_IN_MS = 30000;
        private const int THREAD_SLEEP_IN_MS = 500;

        #endregion Private Constants

        #region Private Variables

        // Indicates is a stop request has been made
        private bool _stopRequested;
        private Thread _taskScheduler_Thread;

        private readonly string _plugin_Folder_Path = string.Empty;

        private WorkQueue _workQueue= new WorkQueue();

        #endregion Private Variables

        #region Private Enums


        #endregion Private Enums

        #region Constructors

        /// <summary>
        /// Starts the task scheduler using the default (\Plugin) folder as the plugin path.
        /// </summary>
        public TaskPoller()
        {
            _plugin_Folder_Path = Path.Combine(Environment.CurrentDirectory, Properties.Resources.Default_Plugin_Folder);
            _log.DebugFormat(Properties.Resources.Msg_TaskPoller_Constructor, _plugin_Folder_Path);
        }

        /// <summary>
        /// Starts the task scheduler using the specified path as the plugin path.
        /// </summary>
        /// <param name="pluginPath">The fully qualified path to the tasks that need to be executed.</param>
        public TaskPoller(string pluginPath)
        {
            _plugin_Folder_Path = TaskPluginInterface.Utilities.GetPluginFolder(pluginPath);
            _log.DebugFormat(Properties.Resources.Msg_TaskPoller_Constructor, _plugin_Folder_Path);
        }

        #endregion Constructors

        #region Public Methods

        #region Start
        /// <summary>
        /// Starts the Task Service
        /// </summary>
        public void Start()
        {
            _log.Debug(Properties.Resources.Msg_StartingThread);
            _stopRequested = false;
            _taskScheduler_Thread = new Thread(startTasks) {Name = Properties.Resources.Thread_Name};
            _taskScheduler_Thread.Start();
            _log.Debug(Properties.Resources.Msg_StartedThread);
        }
        #endregion Start

        #region Shutdown

        /// <summary>
        /// Shuts down the Task Service and performs any cleanup
        /// </summary>
        public void Shutdown()
        {
            _log.Debug(Properties.Resources.Msg_StoppingThread);
            _stopRequested = true;

            // Give Process() time to complete.
            DateTime waitUntil = DateTime.Now.AddMilliseconds(WAIT_TIME_IN_MS);
            do
            {
                Thread.Sleep(THREAD_SLEEP_IN_MS);
            }
            while (_taskScheduler_Thread.IsAlive && DateTime.Now < waitUntil);

            if (_taskScheduler_Thread.IsAlive)
            {
                _log.Debug(Properties.Resources.Thread_Force_Abort);
                try { _taskScheduler_Thread.Abort(); }
                catch { }
            }

            _log.Debug(Properties.Resources.Msg_StoppedThread);

        }

        #endregion Shutdown

        #endregion Public Methods

        #region Private Methods

        #region startTasks
        /// <summary>
        /// Checks for new data files.
        /// </summary>
        private void startTasks()
        {

            try
            {
                _log.Debug(Properties.Resources.Msg_StartTasks_Starting);
                _log.InfoFormat(Properties.Resources.Msg_StartTasks_LookingForTasks, _plugin_Folder_Path);
                // Get a list of available plugins
                List<FileInfo> plugins = TaskPluginInterface.Utilities.GetPluginFileList(_plugin_Folder_Path);
                if (plugins.Count > 1)
                {
                    _log.InfoFormat(Properties.Resources.Found_Plugins, plugins.Count);
                }
                else if (plugins.Count == 1)
                {
                    _log.InfoFormat(Properties.Resources.Found_Plugin, plugins.Count);
                }
                else
                {
                    _log.InfoFormat(Properties.Resources.Found_NoPlugins);
                    return;
                }

                // Setup the work queue
                _log.Debug(Properties.Resources.Msg_StartTasks_SettingUpQueue);
                _workQueue = new WorkQueue {ConcurrentLimit = plugins.Count};
                _workQueue.AllWorkCompleted += workQueue_AllWorkCompleted;
                _workQueue.WorkerException += workQueue_WorkerException;
                _workQueue.RunningWorkItem += workQueue_RunningWorkItem;
                _workQueue.CompletedWorkItem += workQueue_CompletedWorkItem;
                ((WorkThreadPool)_workQueue.WorkerPool).MinThreads = plugins.Count;
                ((WorkThreadPool)_workQueue.WorkerPool).MaxThreads = plugins.Count;

                foreach (FileInfo pluginFilename in plugins)
                {
                    // Now that we have a list of all of the plug-in files, let's create them.
                    List<string> pluginNames = TaskPluginInterface.Utilities.GetPlugins(pluginFilename.FullName);

                    foreach (string pluginClassName in pluginNames)
                    {
                        _log.DebugFormat(Properties.Resources.Msg_Found_PluginInFile, pluginClassName, pluginFilename);
                        // Get the actual plug-in
                        TaskPluginInterface.ITaskPlugin plugin = TaskPluginInterface.Utilities.GetPlugin(pluginFilename.FullName, pluginClassName);

                        if (plugin == null)
                        {
                            _log.ErrorFormat(Properties.Resources.Msg_CouldNotLoadPlugin, pluginClassName, pluginFilename);
                            continue;
                        }

                        // Establish the event handlers for the plug-in
                        _log.DebugFormat(Properties.Resources.Msg_StartTasks_CreatePluginEvents, pluginClassName);
                        plugin.EventStart += plugin_EventStart;
                        plugin.EventProcessing += plugin_EventProcessing;
                        plugin.EventExceptionOccurred += plugin_EventExceptionOccurred;
                        plugin.EventEnd += plugin_EventEnd;

                        // Create a Task (WorkItem)
                        _log.InfoFormat(Properties.Resources.Msg_StartTasks_CreatingTask, pluginClassName);
                        Task pluginTask = new Task(plugin);

                        // Add the Task to the Queue
                        _log.DebugFormat(Properties.Resources.Msg_StartTasks_AddingPluginToWorkQueue, pluginClassName);
                        _workQueue.Add(pluginTask);
                    }   // foreach pluginClassName
                }   // foreach pluginFileName
            }   // try

            catch (Exception ex)
            {
                _log.Error(ex.Message, ex);
            }

            while (!_stopRequested)
            {
                Thread.Sleep(200);
            }

        } // startTasks

        #endregion startTasks

        #endregion Private Methods

        #region Private Helper Methods


        #endregion Private Helper Methods

        #region _workQueue Events

        static void workQueue_CompletedWorkItem(object sender, WorkItemEventArgs e)
        {
            _log.DebugFormat(Properties.Resources.Msg_WorkQueue_CompletedWorkItem, ((Task)e.WorkItem).Plugin.Name);
        }

        static void workQueue_RunningWorkItem(object sender, WorkItemEventArgs e)
        {
            _log.DebugFormat(Properties.Resources.Msg_WorkQueue_CompletedWorkItem, ((Task)e.WorkItem).Plugin.Name);
        }

        static void workQueue_WorkerException(object sender, ResourceExceptionEventArgs e)
        {
            string msg = string.Format(Properties.Resources.Msg_WorkQueue_WorkerException, ((Task)e.WorkItem).Plugin.Name);
            _log.Error(msg, e.Exception);
        }

        static void workQueue_AllWorkCompleted(object sender, EventArgs e)
        {
            _log.Debug(Properties.Resources.Msg_WorkQueue_AllWorkComplete);
        }

        #endregion _workQueue Events

        #region Plug-in Events

        static void plugin_EventEnd(object sender, TaskPluginInterface.PluginEventArgs e)
        {
            _log.InfoFormat(Properties.Resources.Msg_plugin_EventEnd, ((TaskPluginInterface.ITaskPlugin)sender).Name );
        }

        static void plugin_EventExceptionOccurred(object sender, TaskPluginInterface.PluginEventArgs e)
        {
            _log.Error(Properties.Resources.Msg_plugin_EventExceptionOccurred, e.RaisedException);
        }

        void plugin_EventProcessing(object sender, TaskPluginInterface.PluginEventArgs e)
        {
            e.Cancel = _stopRequested;
            
            string msg = string.Format(Properties.Resources.Msg_plugin_EventProcessing,
                ((TaskPluginInterface.ITaskPlugin)sender).Name,
                e.Progress.CurrentItem,
                e.Progress.TotalItems,
                e.Progress.CurrentTask,
                e.Progress.TotalTasks,
                e.Message);
            
            _log.DebugFormat(msg);
        }

        static void plugin_EventStart(object sender, TaskPluginInterface.PluginEventArgs e)
        {
            _log.InfoFormat(Properties.Resources.Msg_plugin_EventStart, ((TaskPluginInterface.ITaskPlugin)sender).Name);
        }   
        #endregion Plug-in Events


    }
}
